/**
 * Encapsule in GraphApp.Controller in order to keep the global namespace
 * clear.
 */
GraphApp.Controller = (function() {
  /**
   * Global variables
   */ 
  // The currently displayed graph.
  var Graph;
  // Set of selectable objects
  var selectableObjects;
  // Identifier of the currently selected object
  var actSelectedId;
  // YUI slider object for setting the animation speed.
  var slider;
  // Speed of the traverse animation
  var speed = 1;
  // Index of the current node during traversion.
  var actVisitedIndex = 0;
  // Search string
  var searchValue;
  // Traverse algorithm ('BFS' || 'DFS')
  var algorithm;
  // Path for the default graph
  var fileName = "samples/sample_tree.json";
  // Diameter of the graph nodes
  var nodeSize = 60;
  // saves the from node for creating a new edge
  var tmpNode;
  // drawing object for encapsulating the graphs edges.
  var graphGraphic;
  // Stores wheter the program stays in current editing state or returns to idle
  var keepState = false;

  /**
   * Traverse state descriptions:
   * running: the algorithm is actually running
   * stop: the algorithm is stopped
   * next: just get the next step
   * prev: just get the previous step
   */
  var TraverseEnum = Object.freeze({
    "running": 1, 
    "stop": 2, 
    "next": 3, 
    "prev": 3
  });

  /**
   * State descriptions:
   * idle: Nothing special going on
   * addNode: After click on addNode Button - show input field for node value
   * addEdge: After click on addEdge Button
   * addEddgeStartDefined: The starting node of the edge to create is selected
   * removeEdge: After click on removeEdge Button
   * removeEdgeStartDefined: ...
   * traverse: Show input fields for traverse algorithm
   * animating: during traverse animation
   * generator: generate a new graph
   */
  var StatesEnum = Object.freeze({
    "idle": 1, 
    "addNode": 2, 
    "addEdge": 3, 
    "addEdgeStartDefined": 4, 
    "removeEdge": 5,
    "traverse": 6, 
    "animating": 7, 
    "generator" : 8
  })

  // stores for the current state of the application
  var programState = StatesEnum.idle;
  var traverseState = TraverseEnum.stop;
  
  /**
   * Preload the default graph from a json file and display it and initialize
   * the UI.
   */
  YUI().use('json-parse','json-stringify','node','graphics','slider', function (Y) {
    // import initial graph
    GraphApp.Load = {
      onLoad: function () {
        Graph = new GraphApp.Graph();
        Graph = GraphApp.IO.read(fileName);
        /**
         * Initialize graphic for drawing edges
         */  
        if (graphGraphic === undefined) {
          graphGraphic = new Y.Graphic({
            render: "#graphGraphic"
          });
        }
        /**
         * Define the YUI slider for setting the animation speed of the traversion.
         */
        slider = new Y.Slider({
          length : 100,
          max : 1,
          min : 20,
          value : 1
        });
        slider.render("#speedSlider");     
      }
    };
    GraphApp.Load.onLoad();
    drawGraph();
  });

  /**
   * Change the current program state to newstate. Hides and shows the 
   * necessary parts of the UI according to the new program state.
   * @param {StatesEnum} newstate The new program state for the program
   * to adopt.
   */
  var changeState = function (newstate) {
    YUI().use('node','anim', function(Y) {
      Y.one('#addNodeFormToggle').hide();
      Y.one('#traverseFormToggle').hide();
      Y.one('#generatorFormToggle').hide();
      Y.one('#notifyMultipleEdits').hide();
      Y.one('#deleteEdgesPool').empty();
      Y.all('.notify').setStyle("display","none");
      switch (newstate) {
        case StatesEnum.idle:
          updateNodeSize();
          drawEdges();
          break;
        case StatesEnum.addEdgeStartDefined:
          Y.one('#notifyMultipleEdits').setStyle("display","block");
          break;
        case StatesEnum.addEdge:
          Y.one('#notifyMultipleEdits').setStyle("display","block");
          Y.one('#notifyAddEdge').setStyle("display","block");
          var animNode = Y.one('#notifyAddEdge');
          var animStart = new Y.Anim({
            node: animNode,
            duration: 10,
            to: {
              opacity: "0"
            }
          });
          animStart.run();
          break;
        case StatesEnum.removeEdge:
          Y.one('#notifyMultipleEdits').setStyle("display","block");
          Y.one('#notifyDeleteEdge').setStyle("display","block");
          var animNode = Y.one('#notifyDeleteEdge');
          var animStart = new Y.Anim({
            node: animNode,
            duration: 10,
            to: {
              opacity: "0"
            }
          });
          animStart.run();
          drawDeleteEdges();
          break;
        case StatesEnum.addNode:
          Y.one('#addNodeFormToggle').show();
          break;
        case StatesEnum.traverse:
          Y.one('#traverseFormToggle').show();
          break;
        case StatesEnum.animating:
          break;
        case StatesEnum.generator:
          Y.one('#generatorFormToggle').show();
          break;
        default:
      // do nothing
      }
    });
    programState = newstate;
  }; // end change state
  
  /**
   * Graph drawing functions
   */
  /**
   * Function for drawing the inmemory Graph object in the browser.
   * First draws all nodes of the graph and makes them selectable, then
   * adds the edges between the nodes.
   */
  var drawGraph = function () {
    YUI().use('node', function (Y) {
      // Adjust size of drawing area.
      updateGraphPoolSize();
      
      Y.one('#graphPool').empty();
      // add graph to draw area
      var graphPool = Y.one('#graphPool');
      // draw nodes
      for (var key in Graph.nodes) {
        var id = Graph.nodes[key].id;
        var value = Graph.nodes[key].value;
        var x = Graph.nodes[key].pos.x;
        var y = Graph.nodes[key].pos.y;
        graphPool.append('<div id= "node-' + id + '" data-id="'+id+'" style="left:'+ x +'px; top:'+ y +'px" class="node"><span>'+ value+'</span></div>')
      }
      drawEdges();
      // selectable objects handler (.node)
      // Make nodes selectable.
      selectableObjects= function(e) {
        var node = e.currentTarget;
        actSelectedId = node.getAttribute("data-id");
        var nodes = Y.all('.node');
        nodes.setStyle('backgroundColor', '#333333');
        nodes.removeClass("selected");
        node.setStyle('backgroundColor', '#FFA500');
        node.addClass("selected");

        switch(programState) {
          case StatesEnum.addEdge: 
          case StatesEnum.addEdgeStartDefined:
            addEdge(node);
            break;
          case StatesEnum.removeEdge:
          case StatesEnum.removeEdgeStartDefined:
            removeEdge(node);
            break;
          default:
        // do nothing
        }
      };
      Y.on('click', selectableObjects, '.node');
      changeState(StatesEnum.idle);
    });
  }; // end drawGraph
  
  /**
   * Draw the edges of the graph.
   */
  var drawEdges = function () {
    YUI().use('graphics', function(Y) {
      graphGraphic.clear();
      // draw edges
      for (var key in Graph.nodes) {
        var node = Graph.nodes[key];
        var fromNode = Y.one('#node-' + node.id);
        for (var i in node.children) {
          var toNode = Y.one('#node-' + Graph.neighbors(node.id)[i]);
          var edgeConnector = graphGraphic.addShape({
            type: "path",
            stroke: {
              weight: 2,
              color: "#000"
            },
            id: fromNode.get('id') + 'to' + toNode.get('id')
          });
          drawArrow(edgeConnector, fromNode.getXY(), toNode.getXY());
        }
      }
    })
  }; // end drawEdges
  
  /**
   * Function for drawing an arrow using the shape between the given from and to
   * positions. Note that the point of the arrowhead is not positioned exactly at
   * the given to-point. This is used to offset the radius of the graphs nodes,
   * in order to display the arrowhead correctly at the edge of a node.
   * @params {Shape} shape A Shape element
   * @params {Array} from The start position of the arrow [X, Y]
   * @params {Array} to The end position of the arrow [X, Y]
   */
  var drawArrow = function (shape, from, to) {
    const cos = 0.866;
    const sin = 0.500;  

    var offset = nodeSize / 2;
    var headlength = offset / 2;
    // prettier than using 30
    var noderadius = offset - 1;
    // Calculate positions for arrowhead
    var dx = from[0] - to[0];
    var dy = from[1] - to[1];
    var dxy = Math.sqrt(dx * dx + dy * dy);
    // Normalize
    dx = dx / dxy;
    dy = dy / dxy;

    var p = [to[0] + noderadius * dx, to[1] + noderadius * dy];
    var end1 = [p[0] + headlength * (dx * cos + dy * -sin), p[1] + headlength * (dx * sin + dy * cos)];
    var end2 = [p[0] + headlength * (dx * cos + dy * sin), p[1] + headlength * (dx * -sin + dy * cos)];

    // Draw path
    shape.moveTo(from[0] + offset, from[1] + offset);
    shape.lineTo(p[0] + offset, p[1] + offset);
    shape.lineTo(end1[0] + offset, end1[1] + offset);
    shape.moveTo(p[0] + offset, p[1] + offset);
    shape.lineTo(end2[0] + offset, end2[1] + offset);
    shape.end();
  }; // end drawArrow
  
  var updateGraphPoolSize = function () {
    YUI().use('node', function (Y) {
      // Adjust the graph drawing arrea according to the maximum y-position
      // of a node in the graph.
      var graphHeight = Graph.maxYPos() + 200;
      var heightString = graphHeight + "px";
      Y.one('#graphPool_outer').setStyle('height', heightString);
    });
  };
  
  /**
   * Update the .css definitions of nodes according to the number of 
   * nodes in the current graph. The more nodes in the graph, the smaller
   * a single nodes is displayed in the browser.
   */
  var updateNodeSize = function () {
    YUI().use("node", function(Y) {
      var graphSize = Graph.getNumberOfNodes();
      var nodes = Y.all('.node');
      var spans = Y.all('.node > span');
      if (graphSize < 30) {
        nodes.setStyle('height', '60px');
        nodes.setStyle('width', '60px');
        spans.setStyle('width', '60px');
        spans.setStyle('margin', '20px 0px 0px 0px');
        nodeSize = 60;
      } else if (graphSize < 50) {
        nodes.setStyle('height', '50px');
        nodes.setStyle('width', '50px');
        spans.setStyle('width', '50px');
        spans.setStyle('margin', '15px 0px 0px 0px');
        nodeSize = 50;
      } else if (graphSize < 70) {
        nodes.setStyle('height', '40px');
        nodes.setStyle('width', '40px');
        spans.setStyle('width', '40px');
        spans.setStyle('margin', '10px 0px 0px 0px');
        nodeSize = 40;
      } else if (graphSize < 100) {
        nodes.setStyle('height', '30px');
        nodes.setStyle('width', '30px');
        spans.setStyle('width', '30px');
        spans.setStyle('margin', '5px 0px 0px 0px');
        nodeSize = 30;
      } else {
        nodes.setStyle('height', '20px');
        nodes.setStyle('width', '20px');
        spans.setStyle('width', '20px');
        spans.setStyle('margin', '0px 0px 0px 0px');
        nodeSize = 20;
      }
    });
  }; // end updateNodeSize
  
  /**
   * Draw the delete edge image for all edges in the graph.
   */
  var drawDeleteEdges = function () {
    YUI().use("node", function(Y) {
      var deleteEdgesPool = Y.one('#deleteEdgesPool');
      deleteEdgesPool.empty();
      for (var key in Graph.nodes) {
        var node = Graph.nodes[key];
        var fromNode = Y.one('#node-' + node.id);
        for (var i in node.children) {
          var toNode = Y.one('#node-' + Graph.neighbors(node.id)[i]);
          id = "del" + fromNode.get('id') + "-to" + toNode.get('id');
          var pos = calcDeleteEdgePosition(fromNode.getXY(), toNode.getXY());

          deleteEdgesPool.append('<div id= "' + id + '" style="left:' + pos[0] + 
            'px; top:' + pos[1] + 'px" class="deledge"></div>')

          Y.one('#' + id).on('click', function(e) {
            var id = this.get('id');
            // id like "delnode-5-tonode-10"
            var idarray = id.split("-")
            Graph.removeEdge(idarray[1], idarray[3]);
            
            changeState(StatesEnum.idle);
            if (keepState) {
              changeState(StatesEnum.removeEdge);
            }
          });
        }
      }
    })
  }; // end drawDeleteEdges
  
  /**
   * Calculates the position for positioning the delete edge image
   * for the edge between the two points from and to.
   * @params {Array} from The x and y coordinates of the edges starting node.
   * @params {Array} to The x and y coordinates of the edges end node.
   * @returns {Array} An Array containing the calculated x and y-coordinates
   * for the positioning of the delete edge image.
   */
  var calcDeleteEdgePosition = function (from, to) {
    // Half the nodeSize for correcting node radius.
    // - 8 for correcting the size of the delete edge img.
    var offset = nodeSize / 2 - 8;

    var dx = from[0] - to[0];
    var dy = from[1] - to[1];

    var x = (from[0] + to[0]) / 2 + offset - dx/10;
    var y = (from[1] + to[1]) / 2 + offset - dy/10;
    return [x, y];
  };
  
  /**
   * Add a new edge to node. This function has to be called twice with two different
   * nodes in order to create a new edge between these two nodes.
   * @param {Node} node A concerned node.
   */
  var addEdge = function (node) {
    if (programState == StatesEnum.addEdge) {
      tmpNode = node;
      changeState(StatesEnum.addEdgeStartDefined);
    } else if (programState == StatesEnum.addEdgeStartDefined && tmpNode !== node) {
      Graph.addEdge(tmpNode.get('id').slice(5), node.get('id').slice(5));
      changeState(StatesEnum.idle);
      if (keepState) {  
        changeState(StatesEnum.addEdge)
      }
    }
  };

  /**
   * Animation
   */
  /**
   * Go one step back in the traverse animation
   * @params {Array} steps The step-Array of the traversion.
   */
  var traverseBack = function(steps){
    YUI().use('anim','node', function(Y) {
      actVisitedIndex--;
      var animNode = "#node-"+steps[actVisitedIndex].id;
      var animStart = new Y.Anim({
        node: animNode,
        duration: speed,
        to: {
          backgroundColor: "#FFA500"
        }
      });
      var animEnd = new Y.Anim({
        node: animNode,
        duration: speed,
        to: {
          backgroundColor: "#333333"
        }
      });
      var onStartEnd = function() {
        animEnd.run();
      };
      animStart.on('end', onStartEnd);
      animStart.run();
    });
  };
  
  /**
   * Start a traverse animation over the graph.
   * @params {Array} steps The Array containing the order in which the nodes
   * are traversed.
   * @params {Integer} index The indes of the starting node.
   * @params {String|Integer} searchvalue The value for which is searched.
   * @params
   */
  var traverseAnimation = function(steps, index, searchvalue, single){
    YUI().use('anim','node', function(Y) {
      actVisitedIndex = index;
      if (index < steps.length) {
        var animNode = "#node-"+steps[index].id;
        var animStart = new Y.Anim({
          node: animNode,
          duration: speed,
          to: {
            backgroundColor: "#FFA500"
          }
        });
        var animEnd = new Y.Anim({
          node: animNode,
          duration: speed,
          to: {
            backgroundColor: "#333333"
          }
        });
        var onStartEnd = function() {
          animEnd.run();
        };
        var onEndEnd = function() {
          Y.one(animNode).setStyle('backgroundColor', '#70E500');
          index++;
          traverseAnimation(steps, index, searchvalue);
        };
        var onEndEndSingle = function() {
          Y.one(animNode).setStyle('backgroundColor', '#70E500');
        };

        if(traverseState == TraverseEnum.running){
          animStart.on('end', onStartEnd);
          if(!single){
            animEnd.on('end', onEndEnd);
          }else{
            // single visit
            animEnd.on('end', onEndEndSingle);
            actVisitedIndex++;
          }
          animStart.run();
        }else{
          Y.one(animNode).setStyle('backgroundColor', '#70E500');
          actVisitedIndex++;
        }

        // traverse order
        Y.one('#notifyTraverseOrder').append(steps[index].value + "<br>");
      } else {
        index--;
        if (steps[index].value == searchvalue) {
          var node = Y.one("#node-"+steps[index].id);
          node.addClass("selected");
          node.setStyle('backgroundColor', '#FFA500');
          actSelectedId = steps[index].id;
        } else {
          Y.one('#notifyTraverseOrder').append("Value not found")
        }
      }
    })
  }; // end traverseAnimation

  /**
   * This function generates a new tree-graph. The amount of nodes
   * is set in the input field #generatorValue. If no value is set,
   * then the standard amount is 50.
   */
  var generator = function(){
    YUI().use('node', function (Y) {
      var g = new GraphApp.Graph();
      var num = Y.one('#generatorValue').get( "value" );
      if(num == "undefined"){
        num = 50;
      }
      // set size of nodes
      if (num < 30) {
        nodeSize = 60;
      } else if (num < 50) {
        nodeSize = 50;
      } else if (num < 70) {
        nodeSize = 40;
      } else if (num < 100) {
        nodeSize = 30;
      } else {
        nodeSize = 20;
      }

      var counter = 0;
      var breaker = 1;
      var width = window.innerWidth*1-100;
      var x = width/2;
      var y = 50;
      var size = nodeSize;
      var rowcounter = 0;
      var row = 1;
      
      // generate nodes
      while(counter <= num){
        var actX = x+rowcounter*size+size;
        if(actX >= width){
          // node out of site, go to next line
          y =  y + size + size;
          actX = 30;
          rowcounter = -1;
        }
        g.addNode(counter,actX,y);
        counter++;
        rowcounter++;
        if(counter >= breaker ){
          // new row
          var curbreaker = breaker;
          y = y + size+size;
          
          breaker = breaker * 2 + 1;
          x = x - (breaker-curbreaker) / 2 * size *0.5;
          if(x <= 0){
            x = 30;
          }
          rowcounter = 0;
          row++;
        }
      }
      // set edges
      breaker = 3;
      rowcounter = 0;
      for(var i = 1; i <= num; i++){
        g.addEdge(i, i * 2);
        g.addEdge(i, i * 2+1);
      }
      //draw graph
      Graph = g;
      drawGraph();
    });
  }; // end generator
  
  /**
   * Helper function for loading of a graph.
   * Load the graph with the given path and display it in the browser.
   * @params {String} path The path of the JSON-file of the graph to load.
   */
  var loadOther = function(path) {
    Graph = GraphApp.IO.read(path);
    drawGraph();
    changeState(StatesEnum.idle);
  };

  /**
   * Events encapsuled
   */
  GraphApp.Events = (function(){
    /**
     * Define the dragable area and objects and drag events.
     */
    YUI().use('dd','graphics', function(Y) {
      new Y.DD.Drag({
        node: '.node'
      }).plug(Y.Plugin.DDConstrained, {
        constrain2node: '#graphPool'
      });

      var delegate = new Y.DD.Delegate({
        container: '#graphPool',
        invalid: 'div#graphPool',
        nodes: 'div'
      });

      delegate.dd.plug(Y.Plugin.DDConstrained, {
        constrain2node: '#graphPool'
      });
    
      // Update the inmemory Graph object with its new properties.
      var updateNodeCallback = function(e) {
        updateGraphPoolSize();
      
        var drag = e.target;
        var node = drag.get('node');
        // calculate offset (graph is not drawn at top of the window)
        var offset = Y.one('#graphPool').getXY();
        var x = Math.floor(e.target.lastXY[0] - offset[0]);
        var y = Math.floor(e.target.lastXY[1] - offset[1]);

        // update position in graph logic
        Graph.setCoordinates(node.get('id').slice(5), x, y);

        // update edges
        drawEdges();
        if (programState == StatesEnum.removeEdge) {
          drawDeleteEdges();
        }
      }

      // Handle drag & drop of node
      // update node position in graph logic
      Y.DD.DDM.on('drag:drag', updateNodeCallback);

      /**
       * This is needed as addition to the previous drag:drag event, because
       * otherwise it sometimes happens that the edges are not displayed
       * correctly after fast mouse movements.
       */
      Y.DD.DDM.on('drag:end', updateNodeCallback);
    }); // end drag events
  
    /**
     * Draw the temporary edge during the process of creating a new edge.
     */
    YUI().use("event", function(Y) {
      var graphPool = Y.one('#graphPool');
      graphPool.on("mousemove", function (e) {
        graphGraphic.removeShape(graphGraphic.getShapeById("tmpEdge"));
        if (programState == StatesEnum.addEdgeStartDefined) {
          var edgeConnector = graphGraphic.addShape({
            type: "path",
            stroke: {
              weight: 2,
              color: "#000"
            },
            id: "tmpEdge"
          });        
          drawArrow(edgeConnector, tmpNode.getXY(), [e.pageX - 30, e.pageY - 30]);
        }
      });

      // abort adding new edge
      graphPool.on("dblclick", function (e) {
        if (programState == StatesEnum.addEdgeStartDefined) {
          graphGraphic.removeShape(graphGraphic.getShapeById("tmpEdge"));
        }
        if (keepState) {
          changeState(StatesEnum.addEdge);
        } else {
          changeState(StatesEnum.idle);
        }
      });
    }); // end events for drawing temporary edge      
 
    YUI().use('node','anim','uploader', function(Y) {
      // Animation events
      // Animation Stop
      Y.one('#traverseStop').on('click', function(e) {
        traverseState = TraverseEnum.stop;
      });
      
      // UI Button click events
      // Load
      Y.one('#loadButton').on('click', function(e) {
        var node = Y.one('#load-toggle');
        if(node.hasClass("open")){
          node.removeClass("open");
        }else{
          node.addClass("open");
        }
      });

      // Edit Dropdown
      Y.one('#editButton').on('click', function(e) {
        var node = Y.one('#edit-toggle');
        if(node.hasClass("open")){
          node.removeClass("open");
        }else{
          node.addClass("open");
        }
      });

      // Close Dropdown
      Y.all('.loaded').on('click',function(e){
        Y.one('#load-toggle').removeClass("open");
        Y.one('#edit-toggle').removeClass("open");
      });

      // Generator Event
      Y.one('#generatorButton').on('click', function(e) {
        generator();
        changeState(StatesEnum.idle);
      });

      // Load sample tree.
      Y.one('#graphOne').on('click', function(e) {
        loadOther("samples/sample_tree.json");
      });

      // Load small sample graph.
      Y.one('#graphTwo').on('click', function(e) {
        loadOther("samples/sample_small_graph.json");
      });

      // Load large sample graph.
      Y.one('#graphThree').on('click', function(e) {
        loadOther("samples/sample_large_graph.json");
      });

      // Click on "Neuen Knoten hinzufügen"
      Y.one('#addNodeForm').on('click', function(e) {
        changeState(StatesEnum.addNode);
      });

      // Click on "Generator"
      Y.one('#generatorForm').on('click', function(e) {
        changeState(StatesEnum.generator);
      });

      // Click on "Add"
      Y.one('#addNodeButton').on('click', function(e) {
        var graphPool = Y.one('#graphPool');
        var value = Y.one('#addNodeValue').get( "value" );
        // Make sure there is a value
        if (value !== '') {
          // add node to graph
          var node = Graph.addNode(value, 0, 0);
          if (node) {
            var elem = graphPool.append('<div id= "node-' + node.id + '" data-id="'+node.id+'"  class="node"><span>'+value+'</span></div>');

            Y.on('click', selectableObjects, '#node-' + node.id);
            Y.one('#addNodeValue').set("value","");
          } else {
            // TODO error message
            alert('Error');
          }
          changeState(StatesEnum.idle);
        }
      });

      // Click on "Knoten löschen"
      // Deletes the previous selected node
      Y.one('#deleteNodeButton').on('click', function(e) {
        var graphPool = Y.one('#graphPool');
        var nodeToDelete = graphPool.one('.selected');

        if (nodeToDelete !== null) {
          // delete node from graph
          Graph.removeNode(nodeToDelete.get('id').slice(5));

          nodeToDelete.remove();
        } else {
          alert("Please select the node you want to delete first.");
        }
        changeState(StatesEnum.idle);
      });

      // Click on "Kante hinzufügen"
      Y.one('#addEdgeButton').on('click', function(e) {
        changeState(StatesEnum.addEdge);
      });

      // Click on "Kante löschen"
      Y.one('#deleteEdgeButton').on('click', function(e) {
        changeState(StatesEnum.removeEdge);
      });
      
      // Events for locking program in edit edge states
      Y.one('#multipleEditsOn').on('click', function(e) {
        keepState = true;
        Y.one('#multipleEditsOn').addClass('btn-success');
        Y.one('#multipleEditsOff').removeClass('btn-success');
        //Y.one('#multipleEditsOn').setStyle('class', 'btn btn-success');
      });
      
      Y.one('#multipleEditsOff').on('click', function(e) {
        keepState = false;
        Y.one('#multipleEditsOff').addClass('btn-success');
        Y.one('#multipleEditsOn').removeClass('btn-success');
      });

      // Use escape on keyboard.
      // Abort current action and go back to idle state.
      Y.on('keydown', function(e) {
        // if ESC is pressed
        if (e.keyCode === 27) {
          changeState(StatesEnum.idle);
        }
      });

      // traverse button action
      Y.one('#traverseButton').on('click', function(e) {
        if(!actSelectedId){
          alert("Please select first a starting node!");
          return;
        } else {
          changeState(StatesEnum.traverse);
        }
      });

      Y.one('#traverseFormHide').on('click', function(e) {
        changeState(StatesEnum.idle);
      });

      Y.one('#traverseButtonAction').on('click', function(e) {
        // display traverse order
        Y.all('.notify').setStyle("display","none");
        Y.one('#notifyTraverseOrderOuter').setStyle("display","block");
        Y.one('#notifyTraverseOrder').empty();

        changeState(StatesEnum.animating);

        algorithm = Y.one('input[name=traverseAlgo]:checked').get("value");
        if(!actSelectedId){
          alert("Please select a starting node!");
          return;
        }
        speed = slider.getValue() / 10;
        searchValue = Y.one('#searchValue').get("value");
        if (searchValue != '') {
          var trav = new GraphApp.Traverse.Traverser(Graph); // erzeugen eines Traversers mit einem Graphen
          trav.setAlgorithm(algorithm); // einstellen des Suchalgorithmus ("BFS" oder "DFS")

          trav.setStartingPoint(Graph.getNode(actSelectedId)); // festlegen des Startknotens (Achtung!! das ist ein Graphen "node"-Objekt
          trav.setSearchValue(searchValue); // festlegen des Suchstrings
          // Algorithmus, Startpunkt und Suchstring müssend vor dem Start gesetzt werden.

          var steps = trav.traverse();
          traverseState = TraverseEnum.running;
          traverseAnimation(steps, 0, searchValue);
        } else {
          alert("Enter a search value");
        }
      });
      
      // Animation Start
      Y.one('#traverseStart').on('click', function(e) {
        changeState(StatesEnum.animating);

        var trav = new GraphApp.Traverse.Traverser(Graph); // erzeugen eines Traversers mit einem Graphen
        trav.setAlgorithm(algorithm); // einstellen des Suchalgorithmus ("BFS" oder "DFS")

        trav.setStartingPoint(Graph.getNode(actSelectedId)); // festlegen des Startknotens (Achtung!! das ist ein Graphen "node"-Objekt
        trav.setSearchValue(searchValue); // festlegen des Suchstrings
        // Algorithmus, Startpunkt und Suchstring müssend vor dem Start gesetzt werden.

        var steps = trav.traverse();
        traverseState = TraverseEnum.running;
        traverseAnimation(steps, actVisitedIndex, searchValue);
      });

      // Animation Next
      Y.one('#traverseNext').on('click', function(e) {
        changeState(StatesEnum.animating);

        var trav = new GraphApp.Traverse.Traverser(Graph); // erzeugen eines Traversers mit einem Graphen
        trav.setAlgorithm(algorithm); // einstellen des Suchalgorithmus ("BFS" oder "DFS")

        trav.setStartingPoint(Graph.getNode(actSelectedId)); // festlegen des Startknotens (Achtung!! das ist ein Graphen "node"-Objekt
        trav.setSearchValue(searchValue); // festlegen des Suchstrings
        // Algorithmus, Startpunkt und Suchstring müssend vor dem Start gesetzt werden.

        var steps = trav.traverse();
        traverseState = TraverseEnum.running;
        traverseAnimation(steps, actVisitedIndex, searchValue,true);
      });

      // Animation Prev
      Y.one('#traversePrev').on('click', function(e) {
        changeState(StatesEnum.animating);

        var trav = new GraphApp.Traverse.Traverser(Graph); // erzeugen eines Traversers mit einem Graphen
        trav.setAlgorithm(algorithm); // einstellen des Suchalgorithmus ("BFS" oder "DFS")

        trav.setStartingPoint(Graph.getNode(actSelectedId)); // festlegen des Startknotens (Achtung!! das ist ein Graphen "node"-Objekt
        trav.setSearchValue(searchValue); // festlegen des Suchstrings
        // Algorithmus, Startpunkt und Suchstring müssend vor dem Start gesetzt werden.

        var steps = trav.traverse();
        traverseBack(steps);
      });
    });
  }());
}());